//@author A0088498A

package logic;

import java.util.HashMap;

import common.AttributeType;
import common.CommandType;
import common.TaskStatus;

import gui.IUserInterface;

/**
 * Parser class:<br/>
 * This class acts as a interpreter between the users and other processing
 * modules<br/>
 * Parser parses raw command from users to appropriate format, the pass the
 * result to the handlers based on the commandKeyword<br/>
 * Handlers will receive from parser the originalCommand (for storage) and a
 * mapping of AttributeTypes and theirs value
 */

public class Parser {
	private IUserInterface _ui;

	private final String INVALID_MESSAGE = "\nInvalid Syntax. Press F10 for help\n";
	private final String INVALID_ATTRIBUTE = "\nWarning: The attribute \"%s\" is not recognizable\n";
	private final String ATTRIBUTE_PREFIX = "-";

	private String _originalMessage;

	/**
	 * Standard constructor <br/>
	 * The reference for this UserInterface is passed to the Parser for calling
	 * output services<br/>
	 * The UI instantiates this Parser should add itself as the parameter <br/>
	 * i.e:</br> {@code
	 * parser = new Parser(this);
	 * }
	 * 
	 * @param ui
	 *            : the reference of the UI
	 */
	public Parser(IUserInterface ui) {
		_ui = ui;
	}

	/**
	 * Parse the raw Command string from the Users<br/>
	 * Then pass to processCommand()<br/>
	 * The Command is parsed into the keyword and argList<br/>
	 * For example:<br/>
	 * {@code add -name cs2013_project -startingTime 20/03/2014 -endingTime 21/03/2014}
	 * is parsed into:<br/>
	 * 
	 * <pre>
	 * keyword: add
	 * argList:
	 * 	name:	cs2103_project
	 * 	startingTime:	20/03/2014
	 * 	endingTime:		21/03/2014
	 * </pre>
	 * 
	 * @param command
	 *            the raw command from user to be parsed
	 */
	public void parseCommand(String command) {
		// Only do when the command is not null
		if (command != null) {
			// Reflect the command back to console
			_ui.writeMessage(">> " + command + "\n");

			_originalMessage = command;

			// Parse Command into commandType and attributeMapping
			CommandType commandType = extractCommandType(command);
			HashMap<AttributeType, String> attrMapping = extractAttributesFromCommand(command);

			// Process the formatted Command
			processCommand(commandType, attrMapping);
		}
	}

	/**
	 * processCommand will "route" the mapping to the appropriate handler based
	 * on commandType<br/>
	 * 
	 * @param commandType
	 *            the commandType of the Command
	 * @param attrMapping
	 *            the attribute List of the Command
	 */
	private void processCommand(CommandType commandType, HashMap<AttributeType, String> attrMapping) {
		// Route logic - route the command and its data to the appropriate
		// handler
		switch (commandType) {
		case add:
			CreateCommandHandler createCmdHandler = new CreateCommandHandler(_ui, _originalMessage, attrMapping);
			createCmdHandler.start();
			break;
		case update:
			UpdateCommandHandler updateCmdHandler = new UpdateCommandHandler(_ui, _originalMessage, attrMapping);
			updateCmdHandler.start();
			break;
		case delete:
			DeleteCommandHandler deleteCmdHandler = new DeleteCommandHandler(_ui, _originalMessage, attrMapping);
			deleteCmdHandler.start();
			break;
		case search:
		case display:
			SearchCommandHandler searchCmdHandler = new SearchCommandHandler(_ui, _originalMessage, attrMapping);
			searchCmdHandler.execute(commandType);
			break;
		case undo:
			UndoCommandHandler undoCmdHandler = new UndoCommandHandler(_ui, _originalMessage, attrMapping);
			undoCmdHandler.start();
			break;
		case confirm:
			ConfirmCommandHandler confirmCommandHandler = new ConfirmCommandHandler(_ui, _originalMessage, attrMapping);
			// int index =
			// checkAndConvertInteger(attrMapping.get(AttributeType.id).trim());
			confirmCommandHandler.start();
			break;
		case mark:
		case unmark:
		case discard:
			// Override the atrrMapping. Only the taskName is carry over the new
			// override mapping, other attribute is discarded.
			HashMap<AttributeType, String> overrideMapping = new HashMap<AttributeType, String>();
			String taskName = attrMapping.get(AttributeType.name);
			overrideMapping.put(AttributeType.name, taskName);

			// Status is set accordingly
			switch (commandType) {
			case mark:
				overrideMapping.put(AttributeType.status, TaskStatus.done.toString());
				break;
			case unmark:
				overrideMapping.put(AttributeType.status, TaskStatus.undone.toString());
				break;
			case discard:
				overrideMapping.put(AttributeType.status, TaskStatus.discard.toString());
				break;
			default:
				break;
			}

			// Update the Task
			UpdateCommandHandler updateCommandHandler = new UpdateCommandHandler(_ui, _originalMessage, overrideMapping);
			updateCommandHandler.start();
			break;
		case block:
			BlockCommandHandler blockCommandHandler = new BlockCommandHandler(_ui, _originalMessage, attrMapping);
			blockCommandHandler.start();
			break;
		case unblock:
			UnblockCommandHandler unblockCommandHandler = new UnblockCommandHandler(_ui, _originalMessage, attrMapping);
			unblockCommandHandler.start();
			break;
		case exit:
			System.exit(0);
			break;
		case invalid:
			_ui.writeMessage(INVALID_MESSAGE);
			break;
		case clear:
			_ui.clear();
			break;
		default:
			break;
		}
	}

	/**
	 * Extract the first token/word in the command as the commandKeyword Then
	 * convert the commandKeyword into commandType
	 * 
	 * @param command
	 *            the raw command from user
	 * @return the commandType, type invalid if the commandKeyword is not
	 *         recognisable
	 */
	private CommandType extractCommandType(String command) {
		String[] commandTokens = splitIntoTokens(command.trim());

		// The command should not be null or empty, hence split-able
		assert (commandTokens.length > 0);

		String commandKeyword = commandTokens[0];
		CommandType commandType = CommandType.getValueOf(commandKeyword);
		return commandType;
	}

	/**
	 * Extract the attribute from the command and map it to the appropriate
	 * Types</br> i.e:<br/>
	 * The raw command:<br/>
	 * <b> Add -name cs2103_project -type deadline -description this project is
	 * to build a memo -deadline 21/03/2014 </b><br/>
	 * is extracted into:<br/>
	 * 
	 * <pre>
	 * name: cs2103_project
	 * type: deadline
	 * description: this project is to build a memo
	 * deadline: 21/03/2014
	 * </pre>
	 * 
	 * @param command
	 *            the command inputed by the user
	 * @return list mapping of the attribute found and its value
	 */
	private HashMap<AttributeType, String> extractAttributesFromCommand(String command) {
		// create empty mapping
		HashMap<AttributeType, String> attrMapping = new HashMap<AttributeType, String>();

		// split command into tokens
		String[] commandTokens = splitIntoTokens(command.trim());

		// scan through the tokens for attribute keyword
		// DO NOT scan the last token (as attribute needs to follow by value)
		for (int i = 1; i < commandTokens.length - 1; i++) {
			if (commandTokens[i].startsWith(ATTRIBUTE_PREFIX)) {
				String attributeString = commandTokens[i].substring(1); // Remove
																		// the
																		// prefix
				AttributeType attributeType = AttributeType.getValueOf(attributeString);

				if (attributeType != AttributeType.invalid) {
					// found the attribute keyword, now find its value
					String value = "";
					int j = i + 1; // i < commandTokens.length - 1; hence ensure
									// no OutOfIndexException
					assert (j < commandTokens.length); // assert to be sure

					while (j < commandTokens.length && !commandTokens[j].startsWith(ATTRIBUTE_PREFIX)) {
						// if the token is not attribute keyword then add into
						// value
						value += commandTokens[j] + " ";
						j++;
					}

					// put the new found mapping
					attrMapping.put(attributeType, value.trim());
				} else {
					// Attribute type not recognisable? Print warning
					_ui.writeMessage(INVALID_ATTRIBUTE, commandTokens[i]);
				}
			}
		}

		return attrMapping;
	}

	/**
	 * Separate input into tokens<br/>
	 * Tokens are separated into tokens by splitting using white spaces <br/>
	 * 
	 * @param str
	 *            the string to be split
	 * 
	 * @return the array of tokens. null if the str is null
	 */
	private String[] splitIntoTokens(String str) {
		if (str != null) {
			String[] result = str.split("\\s+"); // one or more white spaces
			return result;
		} else {
			return null;
		}
	}
}
